#pragma once

#include "stdafx.h"

#include "util/make_unique.h"

	/*
	 * Raw pointers, static lists, mutexes, templates and type hiding! Worth it? Maybe.
	 * Can be used to pass triggers and/or values across relatively unrelated objects. 
	 * Should be threadsafe, although not *that* high-performance because of continuous locking/unlocking.
	 * A lockfree implementation should be possible, but definately a major headache.
	 */

namespace natrium {
	namespace core {
		//The basic structure of a 'handler' type class; lambda-compatible, doesn't require inheritance
		template <typename T>
		struct EventHandler {
			virtual void operator()(const T& value) = 0;
		};

		class EventChannel {
		private:
			typedef std::mutex Mutex;
			typedef std::lock_guard<Mutex> ScopedLock;
			
			template <typename tEvent>
			struct InternalQueue {
				typedef std::function<void (const tEvent&)> Handler;
				typedef std::vector<Handler> HandlerList;

				static InternalQueue& instance() {
					static InternalQueue qi;
					return qi;
				}

				void addDelegate(Handler handler) {
					ScopedLock lock(mMutex);
					mHandlers.push_back(handler);
				}

				void removeDelegate(const Handler& handler) {
					ScopedLock lock(mMutex);
					auto it = std::find(mHandlers.begin(), mHandlers.end(), handler);
					
					if (it == mHandlers.end())
						throw std::runtime_error("Handler not found");

					mHandlers.erase(it);
				}
				
				template <typename tHandler>
				void addDelegate(tHandler* handlerObject) {
					assert(handlerObject != nullptr);

					ScopedLock lock(mMutex);
					//mHandlers.push_back(std::bind(&tHandler::operator(), *handlerObject)); //'naive' bind, does not work with overloaded member functions
					//mHandlers.push_back(std::bind(static_cast<void(tHandler::*)(const tEvent&)>(&tHandler::operator()), *handlerObject, std::placeholders::_1)); //explicit bind, various issues with noncopyable objects
					mHandlers.push_back([handlerObject](const tEvent& evt) { (*handlerObject)(evt); }); //via lambda capture
				}

				template <typename tHandler>
				void removeDelegate(tHandler* handlerObject) {
					Handler asHandler = std::bind(&tHandler::operator(), *handlerObject);
					
					ScopedLock lock(mMutex);
					auto it = std::find(mHandlers.begin(), mHandlers.end(), asHandler);

					if (it  == mHandlers.end())
						throw std::runtime_error("Handler not found");

					mHandlers.erase(it);
				}

				void broadcast(const tEvent& evt) {
					std::vector<std::function<void (const tEvent&)>> currentList;

					{
						ScopedLock lock(mMutex);
						currentList = mHandlers;
					}

					for (const auto& handler: currentList)
						handler(evt);
				}

				Mutex mMutex;
				HandlerList mHandlers;
			};

		public:
			template <typename tEvent, typename tHandler>
			void add(std::function<void (const tEvent&)> handlerFunc) {
				InternalQueue<tEvent>::instance().addDelegate(handlerFunc);
			}

			template <typename tEvent, typename tHandler>
			void add(tHandler* handlerObject) {
				InternalQueue<tEvent>::instance().addDelegate(handlerObject);
			}

			template <typename tEvent, typename tHandler>
			void remove(std::function<void (const tEvent&)> handlerFunc) {
				InternalQueue<tEvent>::instance().removeDelegate(handlerFunc);
			}

			template <typename tEvent, typename tHandler>
			void remove(tHandler* handlerObject) {
				InternalQueue<tEvent>::instance().removeDelegate(handlerObject);
			}

			template <typename tEvent>
			void broadcast(const tEvent& evt) {
				InternalQueue<tEvent>::instance().broadcast(evt);
			}
		};
	}
}